#include "CinematicCamera.h"
#include "CGlobals.h"
#include "CLevelEditor.h"
#include "CCinematicEditor.h"
#include "CSceneObject.h"
#include "irrlicht.h"
#include "CCinematicCommand.h"
#include "CCinematicThread.h"

extern CGlobals* g;
extern CLevelEditor* editor;

CCinematicCameraAnimator::CCinematicCameraAnimator (ICameraSceneNode* camera)
	:m_cameraThread(0),
	 m_camera(camera)	 
{		
}

CCinematicCameraAnimator::~CCinematicCameraAnimator()
{
	m_cameraPositions.clear();
	m_cameraTargets.clear();
}

bool CCinematicCameraAnimator::isEventReceiverEnabled () const
{
	return false;
}

bool CCinematicCameraAnimator::OnEvent (const SEvent& event)
{	
	// if nobody processed the event, return false
	return false;
}

void CCinematicCameraAnimator::setCameraThread(CCinematicThread* thread)
{
	m_cameraThread = thread;
	m_currentTime = 0;	
	m_lastTime = -1;
	m_bFinish = false;

	if(thread==0)
		return;

	//compute and store camera positions
	m_cameraPositions.clear();
	m_cameraTargets.clear();

	int keyFrameNum = m_cameraThread->m_ThreadCinematics->size();	
	for(int i=0; i<keyFrameNum; ++i)
	{
		CCinematicCommand* cmd = (*m_cameraThread->m_ThreadCinematics)[i];
		irr::core::vector3df target = cmd->m_Attributes->getAttributeAsVector3d("target");
		irr::core::vector3df dir = cmd->m_Attributes->getAttributeAsVector3d("dir");
		float dis = cmd->m_Attributes->getAttributeAsFloat("Distance");
		irr::core::vector3df camPos = target-dir*dis;
		m_cameraPositions.push_back(camPos);
		m_cameraTargets.push_back(target);
	}
}

inline irr::s32 clamp(irr::s32 idx, irr::s32 size)
{
	return ( idx<0 ? 0/*size+idx*/ : ( idx>=size ? idx-size : idx ) );
}

irr::core::vector3df doHermit(int key1, int key2, float percent, const std::vector<irr::core::vector3df>& curvePoints)
{
	const irr::u32 pSize = curvePoints.size();

	const irr::core::vector3df& p0 = curvePoints[ clamp( key1 - 1, pSize ) ];
	const irr::core::vector3df& p1 = curvePoints[ clamp( key1 + 0, pSize ) ]; // starting point
	const irr::core::vector3df& p2 = curvePoints[ clamp( key2 + 0, pSize ) ]; // end point
	const irr::core::vector3df& p3 = curvePoints[ clamp( key2 + 1, pSize ) ];

	float u = percent;
	// hermite polynomials
	const irr::f32 h1 = 2.0f * u * u * u - 3.0f * u * u + 1.0f;
	const irr::f32 h2 = -2.0f * u * u * u + 3.0f * u * u;
	const irr::f32 h3 = u * u * u - 2.0f * u * u + u;
	const irr::f32 h4 = u * u * u - u * u;

	// tangents
	const float Tightness = 0.5f;
	const irr::core::vector3df t1 = ( p2 - p0 ) * Tightness;
	const irr::core::vector3df t2 = ( p3 - p1 ) * Tightness;

	// interpolated point
	return (p1 * h1 + p2 * h2 + t1 * h3 + t2 * h4);
}

void CCinematicCameraAnimator::animateNode (ISceneNode* node, u32 timeMs)
{
	update(timeMs);
}

void CCinematicCameraAnimator::update(u32 timeMs)
{
	if(m_cameraThread==0)
		return;	

	int dt = 0;
	if(m_lastTime>0)
		dt = timeMs-m_lastTime;
	m_lastTime = timeMs;

	if(editor->IsCinematicPause() && !editor->IsCinematicStepOneFrame())
		return;

	if(editor->IsCinematicStepOneFrame())
		dt = 50;

	if(dt>500)//maybe return from pause, cause a big dt
		dt = 50;

	m_currentTime+= dt;

	int keyFrameNum = m_cameraThread->m_ThreadCinematics->size();	
	int key1=m_cameraThread->m_lastExecdKeyFrame, key2=m_cameraThread->m_lastExecdKeyFrame+1;

	for(int i=m_cameraThread->m_lastExecdKeyFrame+1; i<keyFrameNum; i++)
	{
		CCinematicCommand* cmd = (*m_cameraThread->m_ThreadCinematics)[i];
		//DEBUG_OUT("key%d, time=%d\n",i,cmd->m_time);
		if (cmd->m_time <= m_currentTime)
		{
			key1 = i;
			m_cameraThread->m_lastExecdKeyFrame = i;
			break;
		}
	}	

	if (key1 < 0) {
		return;
	}
	assert(key1>=0 && "Camera Thread should set first keyframe!");

	key2 = key1+1;
	if(key2>=keyFrameNum)
	{
		key2--;
		m_bFinish = true;
	}

	if(key1==key2)
	{		
		updateCamera(m_cameraPositions.at(key1), m_cameraTargets.at(key1));		
	}	
	else
	{
		int dtime = (*m_cameraThread->m_ThreadCinematics)[key2]->m_time - (*m_cameraThread->m_ThreadCinematics)[key1]->m_time;					
		float percent = (m_currentTime-(*m_cameraThread->m_ThreadCinematics)[key1]->m_time)/(float)dtime;
		if(dtime<=EVENTS_INTERVAL)
			percent=0;

		CCinematicCommand* cmd1 = (*m_cameraThread->m_ThreadCinematics)[key1];
		bool useCurve = cmd1->m_Attributes->getAttributeAsBool("curve");
		irr::core::vector3df target1 = m_cameraTargets.at(key1);
		irr::core::vector3df pos1 = m_cameraPositions.at(key1);		

		CCinematicCommand* cmd2 = (*m_cameraThread->m_ThreadCinematics)[key2];
		irr::core::vector3df target2 = m_cameraTargets.at(key2);
		irr::core::vector3df pos2 = m_cameraPositions.at(key2);		

		irr::core::vector3df target, pos;
		
		if(useCurve)
		{
			target = doHermit(key1,key2,percent,m_cameraTargets);
			pos = doHermit(key1,key2,percent,m_cameraPositions);
		}
		else
		{		
			target = target1+(target2-target1)*percent;
			pos = pos1+(pos2-pos1)*percent;
		}
		
		updateCamera(pos,target);		
	}
}

void CCinematicCameraAnimator::updateCamera(const irr::core::vector3df& camPos, const irr::core::vector3df& target)
{		
	m_camera->setPosition(camPos);
	m_camera->setTarget(target);
	m_camera->updateAbsolutePosition();
}

bool CCinematicCameraAnimator::isFinished()
{
	return m_bFinish;
}
